#include "device.h"

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <sys/ioctl.h>

#include <linux/fs.h>

//----------------------//
/* Default memory device file */
#if defined(__BEOS__) || defined(__HAIKU__)
#define DEFAULT_MEM_DEV "/dev/misc/mem"
#else
#ifdef __sun
#define DEFAULT_MEM_DEV "/dev/xsvc"
#else
#define DEFAULT_MEM_DEV "/dev/mem"
#endif
#endif

/* Use mmap or not */
#ifndef __BEOS__
#define USE_MMAP
#endif

/* Use memory alignment workaround or not */
#ifdef __ia64__
#define ALIGNMENT_WORKAROUND
#endif

//----------------------//
typedef unsigned char u8;
typedef unsigned short u16;
typedef signed short i16;
typedef unsigned int u32;

/*
 * You may use the following defines to adjust the type definitions
 * depending on the architecture:
 * - Define BIGENDIAN on big-endian systems.
 * - Define ALIGNMENT_WORKAROUND if your system doesn't support
 *   non-aligned memory access. In this case, we use a slower, but safer,
 *   memory access method. This should be done automatically in config.h
 *   for architectures which need it.
 */

#ifdef BIGENDIAN
typedef struct {
	u32 h;
	u32 l;
} u64;
#else
typedef struct {
	u32 l;
	u32 h;
} u64;
#endif

#if defined(ALIGNMENT_WORKAROUND) || defined(BIGENDIAN)
static inline u64 U64(u32 low, u32 high)
{
	u64 self;

	self.l = low;
	self.h = high;

	return self;
}
#endif

/*
 * Per SMBIOS v2.8.0 and later, all structures assume a little-endian
 * ordering convention.
 */
#if defined(ALIGNMENT_WORKAROUND) || defined(BIGENDIAN)
#define WORD(x) (u16)((x)[0] + ((x)[1] << 8))
#define DWORD(x) (u32)((x)[0] + ((x)[1] << 8) + ((x)[2] << 16) + ((x)[3] << 24))
#define QWORD(x) (U64(DWORD(x), DWORD(x + 4)))
#else /* ALIGNMENT_WORKAROUND || BIGENDIAN */
#define WORD(x) (u16)(*(const u16 *)(x))
#define DWORD(x) (u32)(*(const u32 *)(x))
#define QWORD(x) (*(const u64 *)(x))
#endif /* ALIGNMENT_WORKAROUND || BIGENDIAN */

//----------------------//
#define SUPPORTED_SMBIOS_VER 0x030200

#define FLAG_NO_FILE_OFFSET     (1 << 0)
#define FLAG_STOP_AT_EOT        (1 << 1)

#define SYS_FIRMWARE_DIR "/sys/firmware/dmi/tables"
#define SYS_ENTRY_FILE SYS_FIRMWARE_DIR "/smbios_entry_point"
#define SYS_TABLE_FILE SYS_FIRMWARE_DIR "/DMI"

// Probe for EFI interface
#define EFI_NOT_FOUND   (-1)
#define EFI_NO_SMBIOS   (-2)

//----------------------//
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
#define DEFAULT_DISK_PARTITION "/proc/partitions"

//----------------------//
#define OUT_OF_SPEC "<OUT OF SPEC>"
#define BAD_INDEX "<BAD INDEX>"
#define NOT_PRESENT "<Not Present>"
#define NOT_SETTABLE "<Not Settable>"
#define NOT_SPECIFIED "<Not Specified>"
#define NO_MODULE_INSTALLED "<No Module Installed>"
#define UNKNOWN "<Unknown>"

//----------------------//
namespace {
  device::sErrorInfo err_;

  struct dmi_header {
    u8 type;
    u8 length;
    u16 handle;
    u8 *data;
  };

  // Return true if STR starts with PREFIX
  inline bool str_starts_with(const u8 * str, const char * prefix);

  // return (v)sprintf() formatted std::string
  std::string vstrprintf(const char * fmt, va_list ap);

  // return (v)sprintf() formatted std::string
  std::string strprintf(const char * fmt, ...);

  bool checksum(const u8 *buf, size_t len);

  /* Returns true if the buffer contains only printable ASCII characters */
  bool is_printable(const u8 *data, int len);

  /*
  * Reads all of file from given offset, up to max_len bytes.
  * A buffer of at most max_len bytes is allocated by this function, and
  * needs to be freed by the caller.
  * This provides a similar usage model to mem_chunk()
  *
  * Returns a pointer to the allocated buffer, or NULL on error, and
  * sets max_len to the length actually read.
  */
  u8* read_file(off_t base, size_t *max_len, const char *filename);
  int myread(int fd, u8 *buf, size_t count, const char *prefix);

  /*
  * Copy a physical memory chunk into a memory buffer.
  * This function allocates memory.
  */
  u8* mem_chunk(off_t base, size_t len, const char *devmem);

  bool read_sysfs(device::sInfo *info);
  bool read_efi(device::sInfo *info, off_t fp);
  bool scan_memory(device::sInfo *info);
  bool scan_disk(device::sInfo *info);

  // Probe for EFI interface
  int address_from_efi(off_t *address);

  bool legacy_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags);
  bool smbios_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags);
  bool smbios3_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags);

  void dmi_pasre_table(device::sInfo *info, off_t base, u32 len, u16 num, u32 ver, const char *devmem, u32 flags);
  void dmi_table_decode(device::sInfo *info, u8 *buf, u32 len, u16 num, u16 ver, u32 flags);
  void dmi_decode(device::sInfo *info, const struct dmi_header *h, u16 ver);

  void to_dmi_header(struct dmi_header *h, u8 *data);

  /*
  * Several boards have a bug where some type 34 structures have their
  * length incorrectly set to 0x10 instead of 0x0B. This causes the
  * first 5 characters of the device name to be trimmed. It's easy to
  * check and fix, so do it, but warn.
  */
  void dmi_fixup_type_34(struct dmi_header *h);

  const char *dmi_string(const struct dmi_header *dm, u8 s);

  void dmi_set_vendor(device::sInfo *info, const char *s) {}

  void parse_set_bios(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sBiosInfo> &bios);
  void parse_set_board(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sBoardInfo> &board);
  void parse_set_chassis(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sChassisInfo> &chassis);
  void parse_set_system(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sSystemInfo> &system);
  void parse_set_cpus(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sCpuInfo>> &cpus);
  void parse_set_memorys(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sMemoryInfo>> &memorys);
  void parse_set_connectors(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sConnectorInfo>> &connectors);

  std::string dmi_system_uuid(const u8 *p, u16 ver);
  std::string dmi_system_wake_up_type(u8 code);

  std::string dmi_processor_type(u8 code);
  std::string dmi_processor_family(const struct dmi_header *h, u16 ver);

  std::string dmi_memory_device_size(u16 code);
  std::string dmi_memory_device_extended_size(u32 code);
  std::string dmi_memory_device_type(u8 code);
  std::string dmi_memory_device_type_detail(u16 code);

  std::string dmi_port_connector_type(u8 code);
  std::string dmi_port_type(u8 code);
}

namespace {
  inline bool str_starts_with(const u8 * str, const char * prefix) {
    return !strncmp((const char *)str, prefix, strlen(prefix));
  }
  inline bool str_starts_with(const char * str, const char * prefix) {
    return !strncmp((const char *)str, prefix, strlen(prefix));
  }
  
  std::string vstrprintf(const char * fmt, va_list ap) {
    char buf[512];
    vsnprintf(buf, sizeof(buf), fmt, ap);
    buf[sizeof(buf)-1] = 0;
    return buf;
  }

  std::string strprintf(const char * fmt, ...) {
    va_list ap; va_start(ap, fmt);
    std::string str = vstrprintf(fmt, ap);
    va_end(ap);
    return str;
  }

  bool checksum(const u8 *buf, size_t len) {
    u8 sum = 0;
    size_t a;

    for (a = 0; a < len; a++)
      sum += buf[a];
    return (sum == 0);
  }

  bool is_printable(const u8 *data, int len) {
    int i;

    for (i = 0; i < len; i++)
      if (data[i] < 32 || data[i] >= 127)
        return false;

    return true;
  }
 
  u8* read_file(off_t base, size_t *max_len, const char *filename) {
    struct stat statbuf;
    int fd;
    u8 *p;

    if ((fd = open(filename, O_RDONLY)) == -1) {
      auto err = strprintf("%s:%s", filename, strerror(errno));
      err_.reset(errno, err);
      return nullptr;
    }

    /*
    * Check file size, don't allocate more than can be read.
    */
    if (fstat(fd, &statbuf) == 0) {
      if (base >= statbuf.st_size) {
        auto err = strprintf("%s: Can't read data beyond EOF"
          , filename);
        err_.reset(-1, err);
        close(fd);
        return nullptr;
      }
      if (*max_len > (size_t)statbuf.st_size - base)
        *max_len = statbuf.st_size - base;
    }

    if ((p = (u8*)malloc(*max_len)) == nullptr) {
      auto err = strprintf("%s: %s"
        , filename, strerror(ENOMEM));
      err_.reset(ENOMEM, err);
      close(fd);
      return nullptr;
    }

    if (lseek(fd, base, SEEK_SET) == -1) {
      auto err = strprintf("%s:%s", filename, strerror(errno));
      err_.reset(errno, err);
      free(p);
      close(fd);
      return nullptr;
    }

    if (myread(fd, p, *max_len, filename) != 0) {
      free(p);
      close(fd);
      return nullptr;
    }
    
    close(fd);
    return p;
  }
  int myread(int fd, u8 *buf, size_t count, const char *prefix) {
    ssize_t r = 1;
    size_t r2 = 0;

    while (r2 != count && r != 0) {
      r = read(fd, buf + r2, count - r2);
      if (r == -1) {
        if (errno != EINTR) {
          auto err = strprintf("%s:%s", prefix, strerror(errno));
          err_.reset(errno, err);
          return -1;
        }
      } else {
        r2 += r;
      }
    }

    if (r2 != count) {
      auto err = strprintf("%s: Unexpected end of file\n", prefix);
      err_.reset(-1, err);
      return -1;
    }

    return 0;
  }

  u8* mem_chunk(off_t base, size_t len, const char *devmem) {
    u8 *p;
    int fd;
    #ifdef USE_MMAP
      struct stat statbuf;
      off_t mmoffset;
      void *mmp;
    #endif

    if ((fd = open(devmem, O_RDONLY)) == -1) {
      auto err = strprintf("%s:%s", devmem, strerror(errno));
      err_.reset(errno, err);
      return NULL;
    }

    if ((p = (u8*)malloc(len)) == NULL) {
      auto err = strprintf("%s: %s"
        , devmem, strerror(ENOMEM));
      err_.reset(ENOMEM, err);
      goto out;
    }

    #ifdef USE_MMAP
      if (fstat(fd, &statbuf) == -1) {
        auto err = strprintf("%s: %s", devmem, strerror(errno));
        err_.reset(errno, err);
        goto err_free;
      }

      /*
      * mmap() will fail with SIGBUS if trying to map beyond the end of
      * the file.
      */
      if (S_ISREG(statbuf.st_mode) && base + (off_t)len > statbuf.st_size) {
        auto err = strprintf("%s: Can't map beyond end of file", devmem);
        err_.reset(-1, err);
        goto err_free;
      }

      #ifdef _SC_PAGESIZE
        mmoffset = base % sysconf(_SC_PAGESIZE);
      #else
        mmoffset = base % getpagesize();
      #endif /* _SC_PAGESIZE */

      /*
      * Please note that we don't use mmap() for performance reasons here,
      * but to workaround problems many people encountered when trying
      * to read from /dev/mem using regular read() calls.
      */
      mmp = mmap(NULL, mmoffset + len, PROT_READ, MAP_SHARED, fd, base - mmoffset);
      if (mmp == MAP_FAILED)
        goto try_read;

      memcpy(p, (u8 *)mmp + mmoffset, len);

      if (munmap(mmp, mmoffset + len) == -1) {
        auto err = strprintf("%s: %s", devmem, strerror(errno));
        err_.reset(errno, err);
      }

      goto out;

    try_read:
    #endif /* USE_MMAP */
      if (lseek(fd, base, SEEK_SET) == -1) {
        auto err = strprintf("%s: %s", devmem, strerror(errno));
        err_.reset(errno, err);
        goto err_free;
      }

      if (myread(fd, p, len, devmem) == 0)
        goto out;

    err_free:
      free(p);
      p = NULL;

    out:
      if (close(fd) == -1)
        perror(devmem);

      return p;
  }

  bool read_sysfs(device::sInfo *info) {
    if (!info)
      return false;

    /*
    * The entry point file could
    * contain one of several types of entry points, so read enough for
    * the largest one, then determine what type it contains.
    */
    int found = 0;
   	size_t size = 0x20;
    u8 *buf = read_file(0, &size, SYS_ENTRY_FILE);
    if (!buf) return false;

    if (size >= 24 && str_starts_with(buf, "_SM3_")) {
      if (smbios3_decode(info, buf, SYS_TABLE_FILE, FLAG_NO_FILE_OFFSET))
        found++;
    } else if (size >= 31 && str_starts_with(buf, "_SM_")) {
      if (smbios_decode(info, buf, SYS_TABLE_FILE, FLAG_NO_FILE_OFFSET))
        found++;
    } else if (size >= 15 && str_starts_with(buf, "_DMI_")) {
      if (legacy_decode(info, buf, SYS_TABLE_FILE, FLAG_NO_FILE_OFFSET))
        found++;
    }

    free(buf);
    return !!found;
  }
  bool read_efi(device::sInfo *info, off_t fp) {
    if (!info)
      return false;

    int found = 0;
    u8 *buf = mem_chunk(fp, 0x20, DEFAULT_MEM_DEV);
    if (!buf) return false;

    if (str_starts_with(buf, "_SM3_")) {
      if (smbios3_decode(info, buf, DEFAULT_MEM_DEV, 0))
        found++;
    } else if (str_starts_with(buf, "_SM_")) {
      if (smbios_decode(info, buf, DEFAULT_MEM_DEV, 0))
        found++;
    }

    free(buf);
    return !!found;
  }
  bool scan_memory(device::sInfo *info) {
    if (!info)
      return false;

    int found = 0;
    /* Fallback to memory scan (x86, x86_64) */
    u8 *buf = mem_chunk(0xF0000, 0x10000, DEFAULT_MEM_DEV);
    if (!buf) return false;

    /* Look for a 64-bit entry point first */
    for (off_t fp = 0; fp <= 0xFFE0; fp += 16) {
      if (str_starts_with(buf + fp, "_SM3_")) {
        if (smbios3_decode(info, buf + fp, DEFAULT_MEM_DEV, 0)) {
          found++;
          break;
        }
      }
    }

    /* If none found, look for a 32-bit entry point */
    if (!found) {
      for (off_t fp = 0; fp <= 0xFFF0; fp += 16) {
        if (str_starts_with(buf + fp, "_SM_") && fp <= 0xFFE0) {
          if (smbios_decode(info, buf + fp, DEFAULT_MEM_DEV, 0)) {
            found++;
            break;
          }
        } else if (str_starts_with(buf + fp, "_DMI_")) {
          if (legacy_decode(info, buf + fp, DEFAULT_MEM_DEV, 0)) {
            found++;
            break;
          }
        }
      }
    }

    free(buf);
    return !!found;
  }
  bool scan_disk(device::sInfo *info) {
    if (!info) return false;

    auto disks = device::disk_scan();
    if (!disks) return false;
    if (!info->disks) {
      info->disks = std::make_shared<std::vector<device::sDiskInfo>>();
    }

    for (auto it = disks->begin(); it != disks->end(); ++it) {
      auto disk = device::disk_info(*it);
      if (!disk) continue;
      device::sDiskInfo tmp= *disk.get();
      info->disks->push_back(std::move(tmp));
    }
    return true;
  }

  int address_from_efi(off_t *address) {
    #if defined(__linux__)
      FILE *efi_systab;
      const char *filename;
      char linebuf[64];
    #elif defined(__FreeBSD__)
      char addrstr[KENV_MVALLEN + 1];
    #endif
      const char *eptype;
      int ret;

      *address = 0; /* Prevent compiler warning */

    #if defined(__linux__)
      /*
      * Linux up to 2.6.6: /proc/efi/systab
      * Linux 2.6.7 and up: /sys/firmware/efi/systab
      */
      if ((efi_systab = fopen(filename = "/sys/firmware/efi/systab", "r")) == NULL
      && (efi_systab = fopen(filename = "/proc/efi/systab", "r")) == NULL)
      {
        /* No EFI interface, fallback to memory scan */
        return EFI_NOT_FOUND;
      }
      ret = EFI_NO_SMBIOS;
      while ((fgets(linebuf, sizeof(linebuf) - 1, efi_systab)) != NULL)
      {
        char *addrp = strchr(linebuf, '=');
        *(addrp++) = '\0';
        if (strcmp(linebuf, "SMBIOS3") == 0
        || strcmp(linebuf, "SMBIOS") == 0)
        {
          *address = strtoull(addrp, NULL, 0);
          eptype = linebuf;
          ret = 0;
          break;
        }
      }
      fclose(efi_systab);

      if (ret == EFI_NO_SMBIOS) {
        auto err = strprintf("%s: SMBIOS entry point missing", filename);
        err_.reset(-1, err);
      }
    #elif defined(__FreeBSD__)
      /*
      * On FreeBSD, SMBIOS anchor base address in UEFI mode is exposed
      * via kernel environment:
      * https://svnweb.freebsd.org/base?view=revision&revision=307326
      */
      ret = kenv(KENV_GET, "hint.smbios.0.mem", addrstr, sizeof(addrstr));
      if (ret == -1)
      {
        if (errno != ENOENT) {
          err_.reset(errno);
        }
        return EFI_NOT_FOUND;
      }

      *address = strtoull(addrstr, NULL, 0);
      eptype = "SMBIOS";
      ret = 0;
    #else
      ret = EFI_NOT_FOUND;
    #endif

    return ret;
  }

  bool legacy_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags) {
    if (!checksum(buf, 0x0F))
		  return false;

    dmi_pasre_table(info, DWORD(buf + 0x08), WORD(buf + 0x06), WORD(buf + 0x0C),
      ((buf[0x0E] & 0xF0) << 12) + ((buf[0x0E] & 0x0F) << 8),
      devmem, flags);

    return true;
  }
  bool smbios_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags) {
    /* Don't let checksum run beyond the buffer */
    if (buf[0x05] > 0x20) {
      auto err = strprintf("Entry point length too large (%u bytes, expected %u)."
        , (unsigned int)buf[0x05], 0x1FU);
      err_.reset(-1, err);
      return false;
    }

    if (!checksum(buf, buf[0x05])
      || str_starts_with(buf + 0x10, "_DMI_")
      || !checksum(buf + 0x10, 0x0F)) {
      return false;
    }

    u16 ver = (buf[0x06] << 8) + buf[0x07];
    /* Some BIOS report weird SMBIOS version, fix that up */
    switch (ver) {
      case 0x021F:
      case 0x0221:
        ver = 0x0203;
        break;
      case 0x0233:
        ver = 0x0206;
        break;
    }

    dmi_pasre_table(info, DWORD(buf + 0x18), WORD(buf + 0x16), WORD(buf + 0x1C),
      ver << 8, devmem, flags);

    return true;
  }
  bool smbios3_decode(device::sInfo *info, u8 *buf, const char *devmem, u32 flags) {
    /* Don't let checksum run beyond the buffer */
    if (buf[0x06] > 0x20) {
      auto err = strprintf("Entry point length too large (%u bytes, expected %u)."
        , (unsigned int)buf[0x06], 0x18U);
      err_.reset(-1, err);
      return false;
    }

    if (!checksum(buf, buf[0x06]))
      return false;

    u32 ver = (buf[0x07] << 16) + (buf[0x08] << 8) + buf[0x09];

    u64 offset = QWORD(buf + 0x10);
    if (!(flags & FLAG_NO_FILE_OFFSET) && offset.h && sizeof(off_t) < 8) {
      err_.reset(-1, "64-bit addresses not supported, sorry.");
      return false;
    }

    dmi_pasre_table(info, ((off_t)offset.h << 32) | offset.l,
        DWORD(buf + 0x0C), 0, ver, devmem, flags | FLAG_STOP_AT_EOT);

    return true;
  }

  void dmi_pasre_table(device::sInfo *info, off_t base, u32 len, u16 num, u32 ver, const char *devmem, u32 flags) {
    u8 *buf = nullptr;

    if (ver > SUPPORTED_SMBIOS_VER) {
      auto err = strprintf("SMBIOS implementations newer than version %u.%u.%u."
        , SUPPORTED_SMBIOS_VER >> 16
        , (SUPPORTED_SMBIOS_VER >> 8) & 0xFF
        , SUPPORTED_SMBIOS_VER & 0xFF);
      err_.reset(0, err);
    }

    if (flags & FLAG_NO_FILE_OFFSET) {
      /*
      * When reading from sysfs or from a dump file, the file may be
      * shorter than announced. For SMBIOS v3 this is expcted, as we
      * only know the maximum table size, not the actual table size.
      * For older implementations (and for SMBIOS v3 too), this
      * would be the result of the kernel truncating the table on
      * parse error.
      */
      size_t size = len;
      buf = read_file(flags & FLAG_NO_FILE_OFFSET ? 0 : base,
        &size, devmem);
      if (num && size != (size_t)len) {
        auto err = strprintf("Wrong DMI structures length: %u bytes "
          "announced, only %lu bytes available."
          , len, (unsigned long)size);
        err_.reset(0, err);
      }
      len = size;
    } else {
      buf = mem_chunk(base, len, devmem);
    }

    if (buf == nullptr) {
      std::string err = "Failed to read table, sorry.";

      #ifndef USE_MMAP
        if (!(flags & FLAG_NO_FILE_OFFSET))
          err += "Open macro USE_MMAP.";
      #endif

      err_.reset(-1, err);
      return;
    }
    
    dmi_table_decode(info, buf, len, num, ver >> 8, flags);

    free(buf);
  }
  void dmi_table_decode(device::sInfo *info, u8 *buf, u32 len, u16 num, u16 ver, u32 flags) {
    u8 *data;
    int i = 0;

    data = buf;
    while ((i < num || !num)
      && data + 4 <= buf + len) /* 4 is the length of an SMBIOS structure header */
    {
      u8 *next;
      struct dmi_header h;

      to_dmi_header(&h, data);

      /*
      * If a short entry is found (less than 4 bytes), not only it
      * is invalid, but we cannot reliably locate the next entry.
      * Better stop at this point, and let the user know his/her
      * table is broken.
      */
      if (h.length < 4) {
        auto err = strprintf("Invalid entry length (%u). DMI table is broken! Stop."
          , (unsigned int)h.length);
        err_.reset(-1, err);
        break;
      }
      i++;

      /* In quiet mode, stop decoding at end of table marker */
      if (h.type == 127)
        break;

      /* Look for the next handle */
      next = data + h.length;
      while ((unsigned long)(next - buf + 1) < len
          && (next[0] != 0 || next[1] != 0))
        next++;
      next += 2;

      /* Make sure the whole structure fits in the table */
      if ((unsigned long)(next - buf) > len) {
        data = next;
        break;
      }

      /* assign vendor for vendor-specific decodes later */
      if (h.type == 1 && h.length >= 5)
        dmi_set_vendor(info, dmi_string(&h, data[0x04]));

      /* Fixup a common mistake */
      if (h.type == 34)
        dmi_fixup_type_34(&h);

      dmi_decode(info, &h, ver);

      data = next;

      /* SMBIOS v3 requires stopping at this marker */
      if (h.type == 127 && (flags & FLAG_STOP_AT_EOT))
        break;
    }

    /*
    * SMBIOS v3 64-bit entry points do not announce a structures count,
    * and only indicate a maximum size for the table.
    */
    {
      std::string err;
      if (num && i != num) {
        err += strprintf("Wrong DMI structures count: %d announced, only %d decoded."
          , num, i);
      }
      if ((unsigned long)(data - buf) > len
        || (num && (unsigned long)(data - buf) < len)) {
        err += strprintf("Wrong DMI structures length: %u bytes announced, structures occupy %lu bytes."
          , len, (unsigned long)(data - buf));
      }
      err_.reset(0, err);
    }
  }
  void dmi_decode(device::sInfo *info, const struct dmi_header *h, u16 ver) {
    /*
    * Note: DMI types 37 and 42 are untested
    */
    switch (h->type) {
      /* 7.1 BIOS Information */
      case 0:
        if (info->type & DEVICE_TYPE_BIOS)
          parse_set_bios(h, ver, info->bios);
        break;
 
      /* 7.2 System Information */
      case 1:
        if (info->type & DEVICE_TYPE_SYSTEM)
          parse_set_system(h, ver, info->system);
        break;
      
      /* 7.3 Base Board Information */
      case 2:
        if (info->type & DEVICE_TYPE_BOARD)
          parse_set_board(h, ver, info->board);
        break;

      /* 7.4 Chassis Information */
      case 3:
        if (info->type & DEVICE_TYPE_CHASSIS)
          parse_set_chassis(h, ver, info->chassis);
        break;

      /* 7.5 Processor Information */
      case 4:
        if (info->type & DEVICE_TYPE_CPU)
          parse_set_cpus(h, ver, info->cpus);
        break;

      case 5: break; /* 7.6 Memory Controller Information */
      case 6: break; /* 7.7 Memory Module Information */
      case 7: break; /* 7.8 Cache Information */

      /* 7.9 Port Connector Information */
      case 8:
        if (info->type & DEVICE_TYPE_CONNECTOR)
          parse_set_connectors(h, ver, info->connectors);
        break;

      case 9: break; /* 7.10 System Slots */
      case 10: break; /* 7.11 On Board Devices Information */
      case 11: break; /* 7.12 OEM Strings */
      case 12: break; /* 7.13 System Configuration Options */
      case 13: break; /* 7.14 BIOS Language Information */
      case 14: break; /* 7.15 Group Associations */
      case 15: break; /* 7.16 System Event Log */
      case 16: break; /* 7.17 Physical Memory Array */

      /* 7.18 Memory Device */
      case 17:
        if (info->type & DEVICE_TYPE_MEMORY)
          parse_set_memorys(h, ver, info->memorys);
        break;

      case 18: break; /* 7.19 32-bit Memory Error Information */
      case 19: break; /* 7.20 Memory Array Mapped Address */
      case 20: break; /* 7.21 Memory Device Mapped Address */
      case 21: break; /* 7.22 Built-in Pointing Device */
      case 22: break; /* 7.23 Portable Battery */
      case 23: break; /* 7.24 System Reset */
      case 24: break; /* 7.25 Hardware Security */
      case 25: break; /* 7.26 System Power Controls */
      case 26: break; /* 7.27 Voltage Probe */
      case 27: break; /* 7.28 Cooling Device */
      case 28: break; /* 7.29 Temperature Probe */
      case 29: break; /* 7.30 Electrical Current Probe */
      case 30: break; /* 7.31 Out-of-band Remote Access */
      case 31: break; /* 7.32 Boot Integrity Services Entry Point */
      case 32: break; /* 7.33 System Boot Information */
      case 33: break; /* 7.34 64-bit Memory Error Information */
      case 34: break; /* 7.35 Management Device */
      case 35: break; /* 7.36 Management Device Component */
      case 36: break; /* 7.37 Management Device Threshold Data */
      case 37: break; /* 7.38 Memory Channel */
      case 38: break; /* 7.39 IPMI Device Information */
      case 39: break; /* 7.40 System Power Supply */
      case 40: break; /* 7.41 Additional Information */
      case 41: break; /* 7.42 Onboard Device Extended Information */
      case 42: break; /* 7.43 Management Controller Host Interface */
      case 43: break; /* 7.44 TPM Device */
      case 126: break; /* 7.44 Inactive */
      case 127: break; /* 7.45 End Of Table */
    }
  }

  void to_dmi_header(struct dmi_header *h, u8 *data) {
    h->type = data[0];
    h->length = data[1];
    h->handle = WORD(data + 2);
    h->data = data;
  }

  void dmi_fixup_type_34(struct dmi_header *h) {
    u8 *p = h->data;

    /* Make sure the hidden data is ASCII only */
    if (h->length == 0x10
      && is_printable(p + 0x0B, 0x10 - 0x0B))
    {
      auto err = strprintf("Invalid entry length (%u). Fixed up to %u."
        , 0x10, 0x0B);
      err_.reset(0, err);
      h->length = 0x0B;
    }
  }

  const char *dmi_string(const struct dmi_header *dm, u8 s) {
    char *bp = (char *)dm->data;
    size_t i, len;

    if (s == 0)
      return NOT_SPECIFIED;

    bp += dm->length;
    while (s > 1 && *bp) {
      bp += strlen(bp);
      bp++;
      s--;
    }

    if (!*bp)
      return BAD_INDEX;

    {
      /* ASCII filtering */
      len = strlen(bp);
      for (i = 0; i < len; i++)
        if (bp[i] < 32 || bp[i] == 127)
          bp[i] = '.';
    }

    return bp;
  }
  
  void parse_set_bios(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sBiosInfo> &bios) {
    const u8 *data = h->data;

    if (!bios) {
      bios = std::make_shared<device::sBiosInfo>();
    }

    if (h->length < 0x12)
      return;

    bios->vendor = dmi_string(h, data[0x04]);
    bios->version = dmi_string(h, data[0x05]);
  }
  void parse_set_board(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sBoardInfo> &board) {
    const u8 *data = h->data;

    if (!board) {
      board = std::make_shared<device::sBoardInfo>();
    }

    if (h->length < 0x08)
      return;

    board->manufacturer = dmi_string(h, data[0x04]);
    board->product = dmi_string(h, data[0x05]);
    board->version = dmi_string(h, data[0x06]);
    board->serial = dmi_string(h, data[0x07]);
  }
  void parse_set_chassis(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sChassisInfo> &chassis) {
    const u8 *data = h->data;

    if (!chassis) {
      chassis = std::make_shared<device::sChassisInfo>();
    }

    if (h->length < 0x09)
      return;

    chassis->manufacturer = dmi_string(h, data[0x04]);
    chassis->type = dmi_string(h, data[0x05]);
    chassis->version = dmi_string(h, data[0x06]);
    chassis->serial = dmi_string(h, data[0x07]);

    if (h->length < 0x16 + data[0x13] * data[0x14])
      return;

    chassis->sku_number = dmi_string(h, data[0x15 + data[0x13] * data[0x14]]);
  }
  void parse_set_system(const struct dmi_header *h, u16 ver, std::shared_ptr<device::sSystemInfo> &system) {
    const u8 *data = h->data;

    if (!system) {
      system = std::make_shared<device::sSystemInfo>();
    }

    if (h->length < 0x08)
      return;

    system->manufacturer = dmi_string(h, data[0x04]);
    system->product = dmi_string(h, data[0x05]);
    system->version = dmi_string(h, data[0x06]);
    system->serial = dmi_string(h, data[0x07]);

    if (h->length < 0x19)
      return;

    system->uuid = dmi_system_uuid(data + 0x08, ver);
    system->wake_up_type = dmi_system_wake_up_type(data[0x18]);

    if (h->length < 0x1B)
      return;

    system->sku_number = dmi_string(h, data[0x19]);
    system->family = dmi_string(h, data[0x1A]);
  }
  void parse_set_cpus(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sCpuInfo>> &cpus) {
    const u8 *data = h->data;

    if (!cpus) {
      cpus = std::make_shared<std::vector<device::sCpuInfo>>();
    }

    bool enabled = false;
    if (data[0x18] & (1 << 6)) {
      static bool status[] = {
        false, // "Unknown", /* 0x00 */
        true,  // "Enabled",
        false, // "Disabled By User",
        false, // "Disabled By BIOS",
        false, // "Idle", /* 0x04 */
        false, // OUT_OF_SPEC,
        false, // OUT_OF_SPEC,
        false, // "Other" /* 0x07 */
      };

      if (status[data[0x18] & 0x07])
        enabled = true;
    }
    if (!enabled)
      return;

    device::sCpuInfo cpu;

    if (h->length < 0x1A)
      goto done;

    cpu.type = dmi_processor_type(data[0x05]);
    cpu.family = dmi_processor_family(h, ver);

    cpu.manufacturer = dmi_string(h, data[0x07]);
    cpu.product = dmi_string(h, data[0x10]);

    if (h->length < 0x23)
      goto done;

    cpu.serial = dmi_string(h, data[0x20]);

    if (h->length < 0x28)
      goto done;

    if (data[0x23] != 0) {
      cpu.core_count =
        h->length >= 0x2C && data[0x23] == 0xFF
          ? WORD(data + 0x2A)
          : data[0x23];
    }
    if (data[0x24] != 0) {
      cpu.core_enabled =
        h->length >= 0x2E && data[0x24] == 0xFF
          ? WORD(data + 0x2C)
          : data[0x24];
    }
    if (data[0x25] != 0) {
      cpu.thread_count = 
        h->length >= 0x30 && data[0x25] == 0xFF
          ? WORD(data + 0x2E)
          : data[0x25];
    }

    done:
      cpus->push_back(std::move(cpu));
  }
  void parse_set_memorys(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sMemoryInfo>> &memorys) {
    const u8 *data = h->data;

    if (!memorys) {
      memorys = std::make_shared<std::vector<device::sMemoryInfo>>();
    }

    if (h->length < 0x15)
      return;

    device::sMemoryInfo mem;

    if (h->length >= 0x20 && WORD(data + 0x0C) == 0x7FFF)
			mem.size = dmi_memory_device_extended_size(DWORD(data + 0x1C));
		else
			mem.size = dmi_memory_device_size(WORD(data + 0x0C));

    if (mem.size == NO_MODULE_INSTALLED)
      return;

    mem.type = dmi_memory_device_type(data[0x12]);
    mem.type_detail = dmi_memory_device_type_detail(WORD(data + 0x13));;

    if (h->length < 0x1B)
      goto done;

    mem.manufacturer = dmi_string(h, data[0x17]);
    mem.serial = dmi_string(h, data[0x18]);

    done:
      memorys->push_back(std::move(mem));
  }
  void parse_set_connectors(const struct dmi_header *h, u16 ver, std::shared_ptr<std::vector<device::sConnectorInfo>> &connectors) {
    const u8 *data = h->data;

    if (!connectors) {
      connectors = std::make_shared<std::vector<device::sConnectorInfo>>();
    }

    if (h->length < 0x09)
      return;

    device::sConnectorInfo conn;

    conn.internal_designator = dmi_string(h, data[0x04]);
    conn.internal_type = dmi_port_connector_type(data[0x05]);
    conn.external_designator = dmi_string(h, data[0x06]);
    conn.external_type = dmi_port_connector_type(data[0x07]);
    conn.port_type = dmi_port_type(data[0x08]);

    done:
      connectors->push_back(std::move(conn));
  }

  std::string dmi_system_uuid(const u8 *p, u16 ver) {
    int only0xFF = 1, only0x00 = 1;

    for (int i = 0; i < 16 && (only0x00 || only0xFF); i++) {
      if (p[i] != 0x00) only0x00 = 0;
      if (p[i] != 0xFF) only0xFF = 0;
    }

    if (only0xFF) {
      return NOT_PRESENT;
    }
    if (only0x00) {
      return NOT_SETTABLE;
    }

    /*
    * As of version 2.6 of the SMBIOS specification, the first 3
    * fields of the UUID are supposed to be encoded on little-endian.
    * The specification says that this is the defacto standard,
    * however I've seen systems following RFC 4122 instead and use
    * network byte order, so I am reluctant to apply the byte-swapping
    * for older versions.
    */
    if (ver >= 0x0206)
      return strprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        p[3], p[2], p[1], p[0], p[5], p[4], p[7], p[6],
        p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
    else
      return strprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
        p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
        p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
  }
  std::string dmi_system_wake_up_type(u8 code) {
    /* 7.2.2 */
    static const char *type[] = {
      "Reserved", /* 0x00 */
      "Other",
      "Unknown",
      "APM Timer",
      "Modem Ring",
      "LAN Remote",
      "Power Switch",
      "PCI PME#",
      "AC Power Restored" /* 0x08 */
    };

    if (code <= 0x08)
      return type[code];
    return OUT_OF_SPEC;
  }

  std::string dmi_processor_type(u8 code) {
    /* 7.5.1 */
    static const char *type[] = {
      "Other", /* 0x01 */
      "Unknown",
      "Central Processor",
      "Math Processor",
      "DSP Processor",
      "Video Processor" /* 0x06 */
    };

    if (code >= 0x01 && code <= 0x06)
      return type[code - 0x01];
    return OUT_OF_SPEC;
  }
  std::string dmi_processor_family(const struct dmi_header *h, u16 ver) {
    const u8 *data = h->data;
    unsigned int i, low, high;
    u16 code;

    /* 7.5.2 */
    static struct {
      int value;
      const char *name;
    } family2[] = {
      { 0x01, "Other" },
      { 0x02, "Unknown" },
      { 0x03, "8086" },
      { 0x04, "80286" },
      { 0x05, "80386" },
      { 0x06, "80486" },
      { 0x07, "8087" },
      { 0x08, "80287" },
      { 0x09, "80387" },
      { 0x0A, "80487" },
      { 0x0B, "Pentium" },
      { 0x0C, "Pentium Pro" },
      { 0x0D, "Pentium II" },
      { 0x0E, "Pentium MMX" },
      { 0x0F, "Celeron" },
      { 0x10, "Pentium II Xeon" },
      { 0x11, "Pentium III" },
      { 0x12, "M1" },
      { 0x13, "M2" },
      { 0x14, "Celeron M" },
      { 0x15, "Pentium 4 HT" },

      { 0x18, "Duron" },
      { 0x19, "K5" },
      { 0x1A, "K6" },
      { 0x1B, "K6-2" },
      { 0x1C, "K6-3" },
      { 0x1D, "Athlon" },
      { 0x1E, "AMD29000" },
      { 0x1F, "K6-2+" },
      { 0x20, "Power PC" },
      { 0x21, "Power PC 601" },
      { 0x22, "Power PC 603" },
      { 0x23, "Power PC 603+" },
      { 0x24, "Power PC 604" },
      { 0x25, "Power PC 620" },
      { 0x26, "Power PC x704" },
      { 0x27, "Power PC 750" },
      { 0x28, "Core Duo" },
      { 0x29, "Core Duo Mobile" },
      { 0x2A, "Core Solo Mobile" },
      { 0x2B, "Atom" },
      { 0x2C, "Core M" },
      { 0x2D, "Core m3" },
      { 0x2E, "Core m5" },
      { 0x2F, "Core m7" },
      { 0x30, "Alpha" },
      { 0x31, "Alpha 21064" },
      { 0x32, "Alpha 21066" },
      { 0x33, "Alpha 21164" },
      { 0x34, "Alpha 21164PC" },
      { 0x35, "Alpha 21164a" },
      { 0x36, "Alpha 21264" },
      { 0x37, "Alpha 21364" },
      { 0x38, "Turion II Ultra Dual-Core Mobile M" },
      { 0x39, "Turion II Dual-Core Mobile M" },
      { 0x3A, "Athlon II Dual-Core M" },
      { 0x3B, "Opteron 6100" },
      { 0x3C, "Opteron 4100" },
      { 0x3D, "Opteron 6200" },
      { 0x3E, "Opteron 4200" },
      { 0x3F, "FX" },
      { 0x40, "MIPS" },
      { 0x41, "MIPS R4000" },
      { 0x42, "MIPS R4200" },
      { 0x43, "MIPS R4400" },
      { 0x44, "MIPS R4600" },
      { 0x45, "MIPS R10000" },
      { 0x46, "C-Series" },
      { 0x47, "E-Series" },
      { 0x48, "A-Series" },
      { 0x49, "G-Series" },
      { 0x4A, "Z-Series" },
      { 0x4B, "R-Series" },
      { 0x4C, "Opteron 4300" },
      { 0x4D, "Opteron 6300" },
      { 0x4E, "Opteron 3300" },
      { 0x4F, "FirePro" },
      { 0x50, "SPARC" },
      { 0x51, "SuperSPARC" },
      { 0x52, "MicroSPARC II" },
      { 0x53, "MicroSPARC IIep" },
      { 0x54, "UltraSPARC" },
      { 0x55, "UltraSPARC II" },
      { 0x56, "UltraSPARC IIi" },
      { 0x57, "UltraSPARC III" },
      { 0x58, "UltraSPARC IIIi" },

      { 0x60, "68040" },
      { 0x61, "68xxx" },
      { 0x62, "68000" },
      { 0x63, "68010" },
      { 0x64, "68020" },
      { 0x65, "68030" },
      { 0x66, "Athlon X4" },
      { 0x67, "Opteron X1000" },
      { 0x68, "Opteron X2000" },
      { 0x69, "Opteron A-Series" },
      { 0x6A, "Opteron X3000" },
      { 0x6B, "Zen" },

      { 0x70, "Hobbit" },

      { 0x78, "Crusoe TM5000" },
      { 0x79, "Crusoe TM3000" },
      { 0x7A, "Efficeon TM8000" },

      { 0x80, "Weitek" },

      { 0x82, "Itanium" },
      { 0x83, "Athlon 64" },
      { 0x84, "Opteron" },
      { 0x85, "Sempron" },
      { 0x86, "Turion 64" },
      { 0x87, "Dual-Core Opteron" },
      { 0x88, "Athlon 64 X2" },
      { 0x89, "Turion 64 X2" },
      { 0x8A, "Quad-Core Opteron" },
      { 0x8B, "Third-Generation Opteron" },
      { 0x8C, "Phenom FX" },
      { 0x8D, "Phenom X4" },
      { 0x8E, "Phenom X2" },
      { 0x8F, "Athlon X2" },
      { 0x90, "PA-RISC" },
      { 0x91, "PA-RISC 8500" },
      { 0x92, "PA-RISC 8000" },
      { 0x93, "PA-RISC 7300LC" },
      { 0x94, "PA-RISC 7200" },
      { 0x95, "PA-RISC 7100LC" },
      { 0x96, "PA-RISC 7100" },

      { 0xA0, "V30" },
      { 0xA1, "Quad-Core Xeon 3200" },
      { 0xA2, "Dual-Core Xeon 3000" },
      { 0xA3, "Quad-Core Xeon 5300" },
      { 0xA4, "Dual-Core Xeon 5100" },
      { 0xA5, "Dual-Core Xeon 5000" },
      { 0xA6, "Dual-Core Xeon LV" },
      { 0xA7, "Dual-Core Xeon ULV" },
      { 0xA8, "Dual-Core Xeon 7100" },
      { 0xA9, "Quad-Core Xeon 5400" },
      { 0xAA, "Quad-Core Xeon" },
      { 0xAB, "Dual-Core Xeon 5200" },
      { 0xAC, "Dual-Core Xeon 7200" },
      { 0xAD, "Quad-Core Xeon 7300" },
      { 0xAE, "Quad-Core Xeon 7400" },
      { 0xAF, "Multi-Core Xeon 7400" },
      { 0xB0, "Pentium III Xeon" },
      { 0xB1, "Pentium III Speedstep" },
      { 0xB2, "Pentium 4" },
      { 0xB3, "Xeon" },
      { 0xB4, "AS400" },
      { 0xB5, "Xeon MP" },
      { 0xB6, "Athlon XP" },
      { 0xB7, "Athlon MP" },
      { 0xB8, "Itanium 2" },
      { 0xB9, "Pentium M" },
      { 0xBA, "Celeron D" },
      { 0xBB, "Pentium D" },
      { 0xBC, "Pentium EE" },
      { 0xBD, "Core Solo" },
      /* 0xBE handled as a special case */
      { 0xBF, "Core 2 Duo" },
      { 0xC0, "Core 2 Solo" },
      { 0xC1, "Core 2 Extreme" },
      { 0xC2, "Core 2 Quad" },
      { 0xC3, "Core 2 Extreme Mobile" },
      { 0xC4, "Core 2 Duo Mobile" },
      { 0xC5, "Core 2 Solo Mobile" },
      { 0xC6, "Core i7" },
      { 0xC7, "Dual-Core Celeron" },
      { 0xC8, "IBM390" },
      { 0xC9, "G4" },
      { 0xCA, "G5" },
      { 0xCB, "ESA/390 G6" },
      { 0xCC, "z/Architecture" },
      { 0xCD, "Core i5" },
      { 0xCE, "Core i3" },
      { 0xCF, "Core i9" },

      { 0xD2, "C7-M" },
      { 0xD3, "C7-D" },
      { 0xD4, "C7" },
      { 0xD5, "Eden" },
      { 0xD6, "Multi-Core Xeon" },
      { 0xD7, "Dual-Core Xeon 3xxx" },
      { 0xD8, "Quad-Core Xeon 3xxx" },
      { 0xD9, "Nano" },
      { 0xDA, "Dual-Core Xeon 5xxx" },
      { 0xDB, "Quad-Core Xeon 5xxx" },

      { 0xDD, "Dual-Core Xeon 7xxx" },
      { 0xDE, "Quad-Core Xeon 7xxx" },
      { 0xDF, "Multi-Core Xeon 7xxx" },
      { 0xE0, "Multi-Core Xeon 3400" },

      { 0xE4, "Opteron 3000" },
      { 0xE5, "Sempron II" },
      { 0xE6, "Embedded Opteron Quad-Core" },
      { 0xE7, "Phenom Triple-Core" },
      { 0xE8, "Turion Ultra Dual-Core Mobile" },
      { 0xE9, "Turion Dual-Core Mobile" },
      { 0xEA, "Athlon Dual-Core" },
      { 0xEB, "Sempron SI" },
      { 0xEC, "Phenom II" },
      { 0xED, "Athlon II" },
      { 0xEE, "Six-Core Opteron" },
      { 0xEF, "Sempron M" },

      { 0xFA, "i860" },
      { 0xFB, "i960" },

      { 0x100, "ARMv7" },
      { 0x101, "ARMv8" },
      { 0x104, "SH-3" },
      { 0x105, "SH-4" },
      { 0x118, "ARM" },
      { 0x119, "StrongARM" },
      { 0x12C, "6x86" },
      { 0x12D, "MediaGX" },
      { 0x12E, "MII" },
      { 0x140, "WinChip" },
      { 0x15E, "DSP" },
      { 0x1F4, "Video Processor" },
    };
    /*
    * Note to developers: when adding entries to this list, check if
    * function dmi_processor_id below needs updating too.
    */

    /* Special case for ambiguous value 0x30 (SMBIOS 2.0 only) */
    if (ver == 0x0200 && data[0x06] == 0x30 && h->length >= 0x08) {
      const char *manufacturer = dmi_string(h, data[0x07]);

      if (strstr(manufacturer, "Intel") != NULL
      || strncasecmp(manufacturer, "Intel", 5) == 0)
        return "Pentium Pro";
    }

    code = (data[0x06] == 0xFE && h->length >= 0x2A) ?
      WORD(data + 0x28) : data[0x06];

    /* Special case for ambiguous value 0xBE */
    if (code == 0xBE) {
      if (h->length >= 0x08) {
        const char *manufacturer = dmi_string(h, data[0x07]);

        /* Best bet based on manufacturer string */
        if (strstr(manufacturer, "Intel") != NULL
        || strncasecmp(manufacturer, "Intel", 5) == 0)
          return "Core 2";
        if (strstr(manufacturer, "AMD") != NULL
        || strncasecmp(manufacturer, "AMD", 3) == 0)
          return "K7";
      }

      return "Core 2 or K7";
    }

    /* Perform a binary search */
    low = 0;
    high = ARRAY_SIZE(family2) - 1;

    while (1) {
      i = (low + high) / 2;
      if (family2[i].value == code)
        return family2[i].name;
      if (low == high) /* Not found */
        return OUT_OF_SPEC;

      if (code < family2[i].value)
        high = i;
      else
        low = i + 1;
    }
  }

  std::string dmi_memory_device_size(u16 code) {
    if (code == 0)
      return NO_MODULE_INSTALLED;
    else if (code == 0xFFFF)
      return UNKNOWN;
    else {
      if (code & 0x8000)
        return strprintf("%u kB", code & 0x7FFF);
      else
        return strprintf("%u MB", code);
    }
  }
  std::string dmi_memory_device_extended_size(u32 code) {
    code &= 0x7FFFFFFFUL;

    /*
    * Use the greatest unit for which the exact value can be displayed
    * as an integer without rounding
    */
    if (code & 0x3FFUL)
      return strprintf("%lu MB", (unsigned long)code);
    else if (code & 0xFFC00UL)
      return strprintf("%lu GB", (unsigned long)code >> 10);
    else
      return strprintf("%lu TB", (unsigned long)code >> 20);
  }
  std::string dmi_memory_device_type(u8 code) {
    /* 7.18.2 */
    static const char *type[] = {
      "Other", /* 0x01 */
      "Unknown",
      "DRAM",
      "EDRAM",
      "VRAM",
      "SRAM",
      "RAM",
      "ROM",
      "Flash",
      "EEPROM",
      "FEPROM",
      "EPROM",
      "CDRAM",
      "3DRAM",
      "SDRAM",
      "SGRAM",
      "RDRAM",
      "DDR",
      "DDR2",
      "DDR2 FB-DIMM",
      "Reserved",
      "Reserved",
      "Reserved",
      "DDR3",
      "FBD2",
      "DDR4",
      "LPDDR",
      "LPDDR2",
      "LPDDR3",
      "LPDDR4" /* 0x1E */
    };

    if (code >= 0x01 && code <= 0x1E)
      return type[code - 0x01];
    return OUT_OF_SPEC;
  }
  std::string dmi_memory_device_type_detail(u16 code) {
    /* 7.18.3 */
    static const char *detail[] = {
      "Other", /* 1 */
      "Unknown",
      "Fast-paged",
      "Static Column",
      "Pseudo-static",
      "RAMBus",
      "Synchronous",
      "CMOS",
      "EDO",
      "Window DRAM",
      "Cache DRAM",
      "Non-Volatile",
      "Registered (Buffered)",
      "Unbuffered (Unregistered)",
      "LRDIMM"  /* 15 */
    };

    if ((code & 0xFFFE) == 0) {
      return "None";
    } else {
      int i;
      std::string tmp;
      for (i = 1; i <= 15; i++) {
        if (code & (1 << i)) {
          if (!tmp.empty())
            tmp += " ";
          tmp += detail[i - 1];
        }
      }
      return std::move(tmp);
    }
  }

  
  std::string dmi_port_connector_type(u8 code) {
    /* 7.9.2 */
    static const char *type[] = {
      "None", /* 0x00 */
      "Centronics",
      "Mini Centronics",
      "Proprietary",
      "DB-25 male",
      "DB-25 female",
      "DB-15 male",
      "DB-15 female",
      "DB-9 male",
      "DB-9 female",
      "RJ-11",
      "RJ-45",
      "50 Pin MiniSCSI",
      "Mini DIN",
      "Micro DIN",
      "PS/2",
      "Infrared",
      "HP-HIL",
      "Access Bus (USB)",
      "SSA SCSI",
      "Circular DIN-8 male",
      "Circular DIN-8 female",
      "On Board IDE",
      "On Board Floppy",
      "9 Pin Dual Inline (pin 10 cut)",
      "25 Pin Dual Inline (pin 26 cut)",
      "50 Pin Dual Inline",
      "68 Pin Dual Inline",
      "On Board Sound Input From CD-ROM",
      "Mini Centronics Type-14",
      "Mini Centronics Type-26",
      "Mini Jack (headphones)",
      "BNC",
      "IEEE 1394",
      "SAS/SATA Plug Receptacle",
      "USB Type-C Receptacle" /* 0x23 */
    };
    static const char *type_0xA0[] = {
      "PC-98", /* 0xA0 */
      "PC-98 Hireso",
      "PC-H98",
      "PC-98 Note",
      "PC-98 Full" /* 0xA4 */
    };

    if (code <= 0x23)
      return type[code];
    if (code >= 0xA0 && code <= 0xA4)
      return type_0xA0[code - 0xA0];
    if (code == 0xFF)
      return "Other";
    return OUT_OF_SPEC;
  }
  std::string dmi_port_type(u8 code) {
    /* 7.9.3 */
    static const char *type[] = {
      "None", /* 0x00 */
      "Parallel Port XT/AT Compatible",
      "Parallel Port PS/2",
      "Parallel Port ECP",
      "Parallel Port EPP",
      "Parallel Port ECP/EPP",
      "Serial Port XT/AT Compatible",
      "Serial Port 16450 Compatible",
      "Serial Port 16550 Compatible",
      "Serial Port 16550A Compatible",
      "SCSI Port",
      "MIDI Port",
      "Joystick Port",
      "Keyboard Port",
      "Mouse Port",
      "SSA SCSI",
      "USB",
      "Firewire (IEEE P1394)",
      "PCMCIA Type I",
      "PCMCIA Type II",
      "PCMCIA Type III",
      "Cardbus",
      "Access Bus Port",
      "SCSI II",
      "SCSI Wide",
      "PC-98",
      "PC-98 Hireso",
      "PC-H98",
      "Video Port",
      "Audio Port",
      "Modem Port",
      "Network Port",
      "SATA",
      "SAS" /* 0x21 */
    };
    static const char *type_0xA0[] = {
      "8251 Compatible", /* 0xA0 */
      "8251 FIFO Compatible" /* 0xA1 */
    };

    if (code <= 0x21)
      return type[code];
    if (code >= 0xA0 && code <= 0xA1)
      return type_0xA0[code - 0xA0];
    if (code == 0xFF)
      return "Other";
    return OUT_OF_SPEC;
  }
}

// disk
namespace {
  // remove first and last flag
  std::string trim(const std::string &str, const std::string &flag) {
    int b = str.find_first_not_of(flag);
    int e = str.find_last_not_of(flag);
    return std::move(str.substr(b, e - b + 1));
  }

  // callback be called every line. 
  // callback return true: continue, false: stop 
  bool file_each_line(const char *file, std::function<bool(const std::string &line)> callback, int max_line_len = 256) {
    if (!file || !*file || !callback || max_line_len <= 0)
      return false;
    FILE *f = ::fopen(file, "r");
    if (!f) return false;

    char *buf = (char*)malloc(max_line_len);
    if (!buf) {
      ::fclose (f);
      return false;
    }
    memset(buf, 0, max_line_len);

    while (fgets(buf, max_line_len, f)) {
      if (!callback(buf))
        break;
    }

    free(buf);
    ::fclose (f);
  }

  // read file all content
  bool file_content(const char *file, std::string &content) {
    if (!file || !*file)
      return false;
    FILE *f = ::fopen(file, "r");
    if (!f) return false;

    ::fseek(f, 0, SEEK_END);
    long epos = ::ftell(f);
    ::fseek(f, 0, SEEK_SET);
    long bpos = ::ftell(f);

    long len = epos - bpos;
    char *buf = (char*)malloc(len);
    if (!buf) {
      ::fclose (f);
      return false;
    }
    memset(buf, 0, len);

    ::fread(buf, sizeof(char*), len, f);
    content = buf;

    free(buf);
    ::fclose (f);
    return true;
  }

  // parser key and value
  // key=value,  key = value , key.value and so on. defalut is '='
  bool parse_k_v(const char *str, std::string &key, std::string &value, const char *flag = "=") {
    if (!str || !*str)
      return false;

    std::string src = str;
    auto pos = src.find(flag);
    if (pos == std::string::npos)
      return false;
    key = src.substr(0, pos);
    pos += strlen(flag);
    value = src.substr(pos, src.length() - pos);

    key = trim(key, " ");
    key = trim(key, "\n");
    value = trim(value, " ");
    value = trim(value, "\n");

    return true;
  }
  
  bool parse_file_v(const char *file, std::string &value) {
    if (!file || !*file)
      return false;
    if (!file_content(file, value))
      return false;

    value = trim(value, " ");
    value = trim(value, "\n");

    return true;
  }

  // Return kernel release as integer ("2.6.31" -> 206031)
  unsigned int get_kernel_release() {
    struct utsname u;
    if (uname(&u))
      return 0;
    unsigned x = 0, y = 0, z = 0;
    if (!(sscanf(u.release, "%u.%u.%u", &x, &y, &z) == 3
          && x < 100 && y < 100 && z < 1000             ))
      return 0;
    return x * 100000 + y * 1000 + z;
  }

  // get size in bytes
  bool disk_size(int fd, uint64_t *bytes) {
    auto ver = get_kernel_release();
    if (ver >= 206000
      || (ver >= 204018 && ver < 205000)) {
      return ioctl(fd, BLKGETSIZE64, bytes) >= 0;
    }

    uint32_t size;
    if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
      *bytes = size;
      return true;
    }

    return false;
  }

  bool disk_exist(const char * name) {
    const char *test_name = name;
    // Remove the leading /dev/... if it's there
    static const char dev_prefix[] = "/dev/";
    if (str_starts_with(test_name, dev_prefix))
      test_name += strlen(dev_prefix);

    char buf[128] = { 0 };
    snprintf(buf, 127
      , "/sys/block/%s/uevent"
      , test_name);

    bool bminor = false;
    bool bname = false;
    bool btype = false;
    std::string k,v;
    file_each_line(buf, [&bminor, &bname, &btype, &k, &v, &test_name](const std::string &line) -> bool {
      if (!parse_k_v(line.c_str(), k, v, "="))
        return true;
      if (k == "MINOR" && v == "0")
        bminor = true;
      if (k == "DEVNAME" && v == test_name)
        bname = true;
      if (k == "DEVTYPE" && v == "disk")
        btype = true;
      return true;
    });
    return (bminor && bname && btype);
  }

  // Disk implement
  class DiskImpl {
    public:
      DiskImpl(const char * dev_name, int flags, int retry_flags = -1)
        : dev_(dev_name)
        , fd_(-1)
        , flags_(flags)
        , retry_flags_(retry_flags) {}
      virtual ~DiskImpl() { if (fd_ >= 0) ::close(fd_); }
    public:
      bool is_open() const { return (fd_ >= 0); }
      bool open() {
        if (is_open())
          return true;

        fd_ = ::open(dev_.c_str(), flags_);

        if (fd_ < 0 && errno == EROFS && retry_flags_ != -1)
          // Retry
          fd_ = ::open(dev_.c_str(), retry_flags_);

        if (fd_ < 0) {
          if (errno == EBUSY && (flags_ & O_EXCL)) {
            // device is locked
            err_.reset(EBUSY,
              "The requested controller is used exclusively by another process!\n"
              "(e.g. smartctl or smartd)\n"
              "Please quit the impeding process or try again later...");
            return false;
          }
          err_.reset((errno==ENOENT || errno==ENOTDIR) ? ENODEV : errno, strerror(errno));
          return false;
        }

        if (fd_ >= 0) {
          // sets FD_CLOEXEC on the opened device file descriptor.  The
          // descriptor is otherwise leaked to other applications (mail
          // sender) which may be considered a security risk and may result
          // in AVC messages on SELinux-enabled systems.
          if (-1 == fcntl(fd_, F_SETFD, FD_CLOEXEC)) {
            // TODO: Provide an error printing routine in class smart_interface
            std::string err = strprintf("fcntl(set  FD_CLOEXEC) failed, errno=%d [%s]\n", errno, strerror(errno));
            err_.reset(errno, err);
          }
        }

        return true;
      }
      bool close() {
        // ???
        int fd = fd_; fd_ = -1;
        if (::close(fd) < 0) {
          err_.reset(errno);
          return false;
        }
        return true;
      }
      bool get_info(device::sDiskInfo *info) {
        if (!info)
          return false;

        const char *test_name = dev_.c_str();
        // Remove the leading /dev/... if it's there
        static const char dev_prefix[] = "/dev/";
        if (str_starts_with(test_name, dev_prefix))
          test_name += strlen(dev_prefix);

        char buf[128] = { 0 };

        info->devname = dev_;

        snprintf(buf, 127
          , "/sys/block/%s/device/vendor"
          , test_name);
        parse_file_v(buf, info->manufacturer);

        snprintf(buf, 127
          , "/sys/block/%s/device/model"
          , test_name);
        parse_file_v(buf, info->product);

        std::string tmp;
        snprintf(buf, 127
          , "/sys/block/%s/size"
          , test_name);
        parse_file_v(buf, tmp);
        info->sectors = std::stoull(tmp);

        disk_size(fd_, &info->size);

        return true;
      }
    protected:
      std::string dev_;   //< Device (path)name
      int fd_;            //< filedesc, -1 if not open.
      int flags_;         //< Flags for ::open()
      int retry_flags_;   //< Flags to retry ::open(), -1 if no retry
  };
}

namespace device {
  sErrorInfo::sErrorInfo(int n) { reset(n); }
  sErrorInfo::sErrorInfo(int n, const char *m) { reset(n, m); }
  sErrorInfo::sErrorInfo(int n, const std::string &m) { reset(n, m); }
  void sErrorInfo::reset(int n, const char *m) { no = n; msg = m; }
  void sErrorInfo::reset(int n, const std::string &m) { no = n; msg = m; }
  void sErrorInfo::reset(int n) { no = n; msg = strerror(n); }
  void sErrorInfo::clear() { no = 0; msg.clear(); }

  const sErrorInfo& last_error() { return err_; }

  sInfo get_info(uType type) {
    sInfo info;
    info.type = type;
    err_.clear();

    if (info.type & DEVICE_TYPE_DISK) {
      scan_disk(&info);
    }

    /* First try reading from sysfs tables. */
    if (read_sysfs(&info))
      return info;

    /* Next try EFI (ia64, Intel-based Mac) */
    off_t fp;
    int efi = address_from_efi(&fp);
    if (efi == EFI_NO_SMBIOS)
      return info;

    if (efi == EFI_NOT_FOUND) {
      scan_memory(&info);
    } else {
      read_efi(&info, fp);
    }

    return info;
  }

  std::shared_ptr<std::set<std::string>> disk_scan() {
    err_.clear();

    FILE *f = ::fopen(DEFAULT_DISK_PARTITION, "r");
    if (!f) {
      err_.reset(errno);
      return nullptr;
    }

    auto disks = std::make_shared<std::set<std::string>>();

    char line[100], dev[100];
    int max, min, size;
    char *s;
    while (fgets(line, sizeof(line), f)) {
      if (sscanf(line, " %u %u %u %[^\n]"
        , &max, &min, &size, dev) != 4) {
        continue;
      }
      
      if (!disk_exist(dev))
        continue;

      std::string tmp = "/dev/";
      tmp += dev;
      disks->insert(std::move(tmp));
    }

    ::fclose(f);

    return disks;
  }
  std::shared_ptr<sDiskInfo> disk_info(const std::string devname) {
    err_.clear();
    if (devname.empty()) {
      err_.reset(EINVAL, "param name is null or empty.");
      return nullptr;
    }

    auto info = std::make_shared<sDiskInfo>();
    DiskImpl disk(devname.c_str(), O_RDONLY | O_NONBLOCK);
    if (!disk.open() || !disk.get_info(info.get()))
      return nullptr;
    return info;
  }
}